<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Object</title>
</head>
<body>
    <script>
        /**
         * 参考1：https://blog.csdn.net/Yi_qian1000/article/details/135264247
         * 参考2：https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
         * 
         * js 中有两种主要类型的对象，普通对象和函数对象。普通对象最常见，通过"{}"创建的就是普通对象；通过 new Function 出来的就是函数对象
         * （函数声明、函数表达式创建的为函数对象），我们可以用typeof来区分。（注意：这里函数声明式和表达式不要和对象字面量方式混淆）
         * 
         */ 
        function f1() {}
        var f2 = function() {}
        var f3 = new Function('name');
        var b1 = new f1();
        var b2 = {name : 'Rose'};
        var b3 = new Object();
        console.log('typeof f1 = ', typeof f1);
        console.log('typeof f2 = ', typeof f2);
        console.log('typeof f3 = ', typeof f3);
        console.log('typeof b1 = ', typeof b1);
        console.log('typeof b2 = ', typeof b2);
        console.log('typeof b3 = ', typeof b3);

        // 创建对象的三种方式
        // 第一种，对象的字面量方式
        var obj1 = {
            name: 'Jack',
            age: 26
        };
        console.log('typeof {}, ', typeof obj1);
        // 第二种， Object 构造函数方式
        var obj2 = new Object();
        obj2.name = 'Jack';
        obj2.age = 26;
        console.log('typeof new Object(), ', typeof obj2);
        // 第三种，构造函数方式
        function Person(name, age) {
            this.name = name;
            this.age = age;
            this.say = function() {
                return 'My name is ' + this.name + '.';
            }
        }
        console.log('Person.prototype = ', Person.prototype);
        console.log('Person.constructor = ', Person.constructor);
        console.log('Person.__proto__ = ', Person.__proto__);
        var obj3 = new Person('Jack', 26);
        var obj4 = new Person('Rose', 25);
        console.log('typeof Person, ', typeof Person);
        console.log('typeof new Person, ', typeof obj3);
        console.log('typeof new Person, ', typeof obj4);
        // 在控制台打印如下obj3.say === obj4.say
        console.log('obj3.say === obj4.say, ', obj3.say === obj4.say);
        console.log('obj3.name === obj4.name, ', obj3.name === obj4.name);
        // 实例都有一个构造函数属性（constructor）指向构造函数，通过构造函数创建的对象拥有构造函数内部定义的属性和方法
        console.log('obj3.say() = ', obj3.say());
        console.log('obj4.say() = ', obj4.say());
        // Test是构造函数， obj3 和 obj4 是构造函数 Test 的实例，实例的属性 constructor 指向构造函数 Test 
        console.log(' Test是构造函数， obj3 和 obj4 是构造函数 Test 的实例，实例的属性 constructor 指向构造函数 Test ');
        console.log('obj3.constructor === Person ', obj3.constructor === Person);
        console.log('obj4.constructor === Person ', obj4.constructor === Person);
        console.log('obj4.constructor === obj3.constructor， ', obj4.constructor === obj3.constructor);
        /**
         * prototype 原型对象
         * 每一个对象（函数也是对象）都有一个特殊的属性叫做原型（prototype），它指向另一个对象，
         * 这个对象（Test.prototype）被称为原型对象， 原型对象是用来共享属性和方法的。
         * 1. 原型对象有一个 constructor 属性指向构造函数本身 Test 。
         * 2. 原型对象是一个普通的对象，它包含属性和方法。
         * 3. 原型对象的属性和方法会被继承到所有通过原型链与它相连的对象。
         * 4. 只有函数对象才具有 prototype 这个原型属性。
         */
        Person.prototype.say = function() {
            return 'how to say?';
        }
        Person.prototype.sayHello = function() {
            return 'say hello';
        }
        // 原型上的方法不会覆盖实例的方法，应该是先找实例上的方法，有的话就不去原型上找了。
        console.log('change obj3.say() = ', obj3.say());
        console.log('change obj4.say() = ', obj4.say());
        console.log('obj3.sayHello() = ', obj3.sayHello());
        console.log('obj4.sayHello() = ', obj4.sayHello());
        console.log('obj4.sayHello === obj3.sayHello， ', obj4.sayHello === obj3.sayHello);
        /**
         * __proto__ 隐式原型
         * 每个对象都有一个“ __proto__ ”属性，这个__proto__就被称为隐式原型。实例对象也是对象，也存在__proto__属性。
         * 1. 每个js对象都有一个隐藏的原型对象属性__proto__，它指向创建它的构造函数的原型对象。
         * 2. __proto__存在的意义在于为原型链查找提供方向，原型链查找靠的是__proto__，而不是prototype。
         */
        console.log('obj4.__proto__ === Person.prototype， ', obj4.__proto__ === Person.prototype);
        console.log('obj4.prototype = ', obj4.prototype);
        /**
         * 原型链
         * 1. Person.prototype 的 __proto__ 指向 Object.prototype
         * 2. 所有的对象，包括构造函数的原型对象，最终都指向 Object.prototype ，这是js原型链的顶点。
         * 3. Object.prototype 作为原型链的顶端，它不再有自己的原型，所以 Object.prototype.__proto__ 指向null，表示原型链的终点。
         * 
         * 每个对象都有一个原型( prototype ),它指向另外一个对象，而指向的对象又存在属性 __proto__ 指向另外一个对象。当我们访问对象的属性时，会先在对象定义的属性中进行查找，
         * 没找到就会沿着 __proto__ 一路向上查找，最终形成一个链式结构，这整个链式结构就叫做原型链。如果在原型链中找到了这个属性，就返回找到的属性值；如果整个原型链都没
         * 找到这个属性值，则返回 undefined ，没找到方法直接报错( not a function )。
         * 
         * 
         */
        console.log('Person.prototype.__proto__ = ', Person.prototype.__proto__);
        
        function Student(name, age, school) {
            this.school = school;
            Person.call(this, name, age);
        }
        console.log('Student.prototype = ', Student.prototype);
        console.log('Student.prototype.__proto__ = ', Student.prototype.__proto__);
        var stu1 = new Student('Rose', 25, '1st');
        console.log('stu1.constructor = ', stu1.constructor);
        console.log('stu1.prototype = ', stu1.prototype, "， 这不是继承，只是复制了 Person 的内存结构。");
        console.log('stu1.__proto__ = ', stu1.__proto__);
        console.log('stu1.__proto__.__proto__ = ', stu1.__proto__.__proto__);
        console.log('stu1.say = ', stu1.say(), "， 这里调用的是 Person 内部的方法，不是原型上的方法。");
        console.log('stu1.sayHello = ', stu1.sayHello, "， 这样是访问不到原型上的方法。");
        /**
         * Function 的原型
         * Function作为一个内置对象，代码运行前就存在，并非自己创造自己，先有的Function，然后实现上把原型指向了 Function.prototype 以此来保持和其他函数一致，表明一种关系。
         * 
         */
        Student.prototype.sex = 'man';
        var stu2 = new Student('Jack');
        console.log('stu2.sex = ', stu2.sex);
        console.log('Student.sex = ', Student.sex);
        Object.prototype.sex = 'woman';
        console.log('stu2.sex = ', stu2.sex);
        console.log('Student.sex = ', Student.sex);
        
        Object.prototype.price = 2000;
        Function.prototype.price = 3000;
        console.log('stu2.price = ', stu2.price);
        console.log('Student.price = ', Student.price);
        // ES6 extends 
        class Teacher extends Person {
            constructor(name, age, len) {
                super(name, age);
                this.len = len;
            }

            sayBye() {
                console.log('good bye');
            }
        }

        var t = new Teacher('Jack', 26, 180);
        t.sayBye();
        console.log('t.say() = ', t.say());
        console.log('t.sayHello() = ', t.sayHello);
        console.log('t.constructor = ', t.constructor);
        console.log('t.prototype = ', t.prototype);
        console.log('t.__proto__ = ', t.__proto__);
        console.log('Teacher.constructor = ', Teacher.constructor);
        console.log('Teacher.prototype = ', Teacher.prototype);
        console.log('Teacher.__proto__ = ', Teacher.__proto__);
        // origin extends test
        function Worker(name, age, year) {
            this.year = year;
            Person.call(this, name, age);
        }
        Worker.prototype = Person.prototype;
        Worker.prototype.constructor = Worker;
        console.log('WARN ======= Person.prototype = ', Person.prototype);
        console.log('Worker.constructor = ', Worker.constructor);
        console.log('Worker.prototype = ', Worker.prototype);
        console.log('Worker.__proto__ = ', Worker.__proto__);
        var w = new Worker('Jack', 26, 1999);
        console.log('w.say() = ', w.say());
        console.log('w.sayHello() = ', w.sayHello);
        console.log('w.constructor = ', w.constructor);
        console.log('w.prototype = ', w.prototype);
        console.log('w.__proto__ = ', w.__proto__);

        var f = new Function('name');
        console.log('f.constructor = ', f.constructor);
        console.log('f.prototype = ', f.prototype);
        console.log('f.__proto__ = ', f.__proto__);
        var ff = new f('nnnnn');
        console.log('ff.constructor = ', ff.constructor);
        console.log('ff.prototype = ', ff.prototype);
        console.log('ff.__proto__ = ', ff.__proto__);
        console.log('ff = ', ff.__proto__);

        console.log('w.isPerson = ', w.isPerson());
    </script>
</body>
</html>